{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ic4_occAAiAT"
      },
      "source": [
        "##### Copyright 2020 The TensorFlow Authors."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "ioaprt5q5US7"
      },
      "outputs": [],
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "sPrjeJhFQBmu"
      },
      "source": [
        "# 統合勾配"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hKY4XMc9o8iB"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td><a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/interpretability/integrated_gradients\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\">TensorFlow.org で実行</a></td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs-l10n/blob/master/site/ja/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\">\tGoogle Colab で実行</a>\n",
        "</td>\n",
        "  <td>     <a target=\"_blank\" href=\"https://github.com/tensorflow/docs-l10n/blob/master/site/ja/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\">\tGitHub でソースを表示</a>\n",
        "</td>\n",
        "  <td>     <a href=\"https://storage.googleapis.com/tensorflow_docs/docs-l10n/site/ja/tutorials/interpretability/integrated_gradients.ipynb\"><img src=\"https://www.tensorflow.org/images/download_logo_32px.png\">ノートブックをダウンロード</a>\n",
        "</td>\n",
        "  <td><a href=\"https://tfhub.dev/google/imagenet/inception_v1/classification/4\"><img src=\"https://www.tensorflow.org/images/hub_logo_32px.png\">TF Hub モデルを見る</a></td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NG17_Wp6ikKf"
      },
      "source": [
        "このチュートリアルでは、[説明可能な AI](https://en.wikipedia.org/wiki/Explainable_artificial_intelligence) テクニック「[Axiomatic Attribution for Deep Networks](https://arxiv.org/abs/1703.01365)」という論文で紹介された[説明可能な AI](https://en.wikipedia.org/wiki/Explainable_artificial_intelligence) テクニックである**統合勾配（IG、Integrated Gradients）**の実装方法を実演します。IG は特徴量の観点からモデルの予測間の関係について説明することを目的としています。特徴量重要度の理解、データの歪みの識別、およびモデル性能のデバッグなど、多様なユースケースがあります。\n",
        "\n",
        "IG は、大規模なネットワークや画像などの特徴空間への拡張を可能にする代替アプローチと比較した場合に、あらゆる微分可能モデル（画像、テキスト、構造化データなど）への用途性の高さ、実装しやすさ、理論的正当性、および計算効率性により、利用者の多い解釈可能性テクニックとなりました。\n",
        "\n",
        "このチュートリアルでは、IG の実装を手順を追って説明し、画像分類器のピクセル特徴量重要度を理解します。例として、消防艇が放水しているこちらの[画像](https://commons.wikimedia.org/wiki/File:San_Francisco_fireboat_showing_off.jpg)を使用します。この画像を消防艇として分類し、消防艇と放水器を構成するピクセルを、決定に重要な要素としてハイライトします。モデルは、このチュートリアルの後の方でもこの画像を消防艇として分類しますが、決定を説明する際に、同じピクセルを重要としてハイライトするでしょうか？\n",
        "\n",
        "「IG 属性マスク」と「元の画像 + IG マスクのオーバーレイ」という以下の画像では、モデルが消防艇の放水器と放水を構成するピクセルを（紫色で）ハイライト表示し、消防艇自体よりも決定に重要であることを確認できます。モデルはどのように新しい消防艇を一般化するでしょうか。放水がない消防艇はどのように分類されるでしょうか。IG がどのように機能するのか、また IG をどのようにモデルに適用すれば、予測と基盤の特徴量の関係をさらに理解できるようになるのかについて、以下で説明しています。\n",
        "\n",
        "![Output Image 1](https://gitlocalize.com/repo/4592/ja/site/en-snapshot/tutorials/interpretability/images/IG_fireboat.png)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ppydw6ZbKzM1"
      },
      "source": [
        "## セットアップ"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "cbUMIubipgg0"
      },
      "outputs": [],
      "source": [
        "import matplotlib.pylab as plt\n",
        "import numpy as np\n",
        "import tensorflow as tf\n",
        "import tensorflow_hub as hub"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GVVV4BGrABkA"
      },
      "source": [
        "### トレーニング済みの画像分類器を TF-Hub からダウンロードする"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7kwwJ35xmtoK"
      },
      "source": [
        "IG は、あらゆる微分可能なモデルに適用できます。元の論文の趣旨にしたがい、同じモデルのトレーニング済みバージョンである Inception V1 を使用します。これは [TensorFlow Hub](https://tfhub.dev/google/imagenet/inception_v1/classification/4) からダウンロードします。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "14APZcfHolKj"
      },
      "outputs": [],
      "source": [
        "model = tf.keras.Sequential([\n",
        "                             hub.KerasLayer(name='inception_v1', \n",
        "                                            handle='https://tfhub.dev/google/imagenet/inception_v1/classification/4', \n",
        "                                            trainable=False),\n",
        "                             ])\n",
        "model.build([None, 224, 224, 3])\n",
        "model.summary()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GjLRn2e5xFOb"
      },
      "source": [
        "モジュールページより、Inception V1 について、次の内容に注意しておく必要があります。\n",
        "\n",
        "**入力**: 期待されるモデルの入力形状は `(None, 224, 244, 3,)` です。これは、dtype float32 と形状 `(batch_size, height, width, RGB channels)` の密な 4D テンソルで、要素は 範囲 [0, 1] に正規化されたピクセルの RGB カラー値です。最初の要素は `None` で、モデルは任意のバッチサイズを取ることができることを示します。\n",
        "\n",
        "**出力**: `(batch_size, 1001)` の形状を持つロジットの `tf.Tensor` です。各行は ImageNetの 1,001 クラスごとのモデルの予測スコアを表します。モデルの最上位予測クラスのインデックスには、`tf.argmax(predictions, axis=-1)` を使用できます。さらに、また、`tf.nn.softmax(predictions, axis=-1)`を使用して、モデルのロジット出力をすべてのクラスの予測確率に変換し、モデルの不確実性を定量化して同様の予測クラスをデバッグ用に確認することもできます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "huZnb_O0L9mw"
      },
      "outputs": [],
      "source": [
        "def load_imagenet_labels(file_path):\n",
        "  labels_file = tf.keras.utils.get_file('ImageNetLabels.txt', file_path)\n",
        "  with open(labels_file) as reader:\n",
        "    f = reader.read()\n",
        "    labels = f.splitlines()\n",
        "  return np.array(labels)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "Rtrl-u7T6NEk"
      },
      "outputs": [],
      "source": [
        "imagenet_labels = load_imagenet_labels('https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "STpIJr1Z5r_u"
      },
      "source": [
        "### `tf.image` で画像を読み込んで事前処理する\n",
        "\n",
        "[ウィキメディアコモンズ](https://commons.wikimedia.org/wiki/Main_Page)から[消防艇](https://commons.wikimedia.org/wiki/File:San_Francisco_fireboat_showing_off.jpg)と[ジャイアントパンダ](https://commons.wikimedia.org/wiki/File:Giant_Panda_2.JPG)の 2 つの画像を使用して、IG を説明します。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "YOb0Adq-rU5J"
      },
      "outputs": [],
      "source": [
        "def read_image(file_name):\n",
        "  image = tf.io.read_file(file_name)\n",
        "  image = tf.image.decode_jpeg(image, channels=3)\n",
        "  image = tf.image.convert_image_dtype(image, tf.float32)\n",
        "  image = tf.image.resize_with_pad(image, target_height=224, target_width=224)\n",
        "  return image"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_khLTN75CLMJ"
      },
      "outputs": [],
      "source": [
        "img_url = {\n",
        "    'Fireboat': 'http://storage.googleapis.com/download.tensorflow.org/example_images/San_Francisco_fireboat_showing_off.jpg',\n",
        "    'Giant Panda': 'http://storage.googleapis.com/download.tensorflow.org/example_images/Giant_Panda_2.jpeg',\n",
        "}\n",
        "\n",
        "img_paths = {name: tf.keras.utils.get_file(name, url) for (name, url) in img_url.items()}\n",
        "img_name_tensors = {name: read_image(img_path) for (name, img_path) in img_paths.items()}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "AYIeu8rMLN-8"
      },
      "outputs": [],
      "source": [
        "plt.figure(figsize=(8, 8))\n",
        "for n, (name, img_tensors) in enumerate(img_name_tensors.items()):\n",
        "  ax = plt.subplot(1, 2, n+1)\n",
        "  ax.imshow(img_tensors)\n",
        "  ax.set_title(name)\n",
        "  ax.axis('off')\n",
        "plt.tight_layout()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QvTYc8IZKbeO"
      },
      "source": [
        "### 画像の分類\n",
        "\n",
        "これらの画像を分類して、信頼度の高い上位 3 つの予測を表示することにしましょう。次は、上位 k の予測ラベルと確率を取得するユーティリティ関数です。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5gsO7ILHZ0By"
      },
      "outputs": [],
      "source": [
        "def top_k_predictions(img, k=3):\n",
        "  image_batch = tf.expand_dims(img, 0)\n",
        "  predictions = model(image_batch)\n",
        "  probs = tf.nn.softmax(predictions, axis=-1)\n",
        "  top_probs, top_idxs = tf.math.top_k(input=probs, k=k)\n",
        "  top_labels = imagenet_labels[tuple(top_idxs)]\n",
        "  return top_labels, top_probs[0]"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "l80a8N2vcIP-"
      },
      "outputs": [],
      "source": [
        "for (name, img_tensor) in img_name_tensors.items():\n",
        "  plt.imshow(img_tensor)\n",
        "  plt.title(name, fontweight='bold')\n",
        "  plt.axis('off')\n",
        "  plt.show()\n",
        "\n",
        "  pred_label, pred_prob = top_k_predictions(img_tensor)\n",
        "  for label, prob in zip(pred_label, pred_prob):\n",
        "    print(f'{label}: {prob:0.1%}')"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v-lH1N4timM2"
      },
      "source": [
        "## 統合勾配を計算する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FV7pfZHANaz1"
      },
      "source": [
        "Inception V1 は、ImageNet クラス確率値によって 0 と 1 の間に定義された入力特徴空間、画像ピクセル値、および出力空間のマッピングを説明する学習済みの関数です。ニューラルネットワークの初期の解釈可能性の手法では、モデルの予測関数に沿った特定のポイントで、どのピクセルがモデルの予測に対して最も急であるかを示す勾配を使用して特徴量重要度が割り当てられていました。ただし、勾配は、ピクセル値に関するモデルの予測関数の*局所的*な変更のみを説明しており、モデル予測関数全体を完全には説明していません。モデルが個々のピクセルの範囲と正しい ImageNet クラスとの関係を完全に「学習」するにつれ、このピクセルの勾配は*飽和*します。つまり、徐々に小さくなり、ゼロになることさえあります。 以下の単純なモデル関数を考察しましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0AUkIUvpkaO8"
      },
      "outputs": [],
      "source": [
        "def f(x):\n",
        "  \"\"\"A simplified model function.\"\"\"\n",
        "  return tf.where(x < 0.8, x, 0.8)\n",
        "\n",
        "def interpolated_path(x):\n",
        "  \"\"\"A straight line path.\"\"\"\n",
        "  return tf.zeros_like(x)\n",
        "\n",
        "x = tf.linspace(start=0.0, stop=1.0, num=6)\n",
        "y = f(x)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kMdAKooulVRE"
      },
      "outputs": [],
      "source": [
        "fig = plt.figure(figsize=(12, 5))\n",
        "ax0 = fig.add_subplot(121)\n",
        "ax0.plot(x, f(x), marker='o')\n",
        "ax0.set_title('Gradients saturate over F(x)', fontweight='bold')\n",
        "ax0.text(0.2, 0.5, 'Gradients > 0 = \\n x is important')\n",
        "ax0.text(0.7, 0.85, 'Gradients = 0 \\n x not important')\n",
        "ax0.set_yticks(tf.range(0, 1.5, 0.5))\n",
        "ax0.set_xticks(tf.range(0, 1.5, 0.5))\n",
        "ax0.set_ylabel('F(x) - model true class predicted probability')\n",
        "ax0.set_xlabel('x - (pixel value)')\n",
        "\n",
        "ax1 = fig.add_subplot(122)\n",
        "ax1.plot(x, f(x), marker='o')\n",
        "ax1.plot(x, interpolated_path(x), marker='>')\n",
        "ax1.set_title('IG intuition', fontweight='bold')\n",
        "ax1.text(0.25, 0.1, 'Accumulate gradients along path')\n",
        "ax1.set_ylabel('F(x) - model true class predicted probability')\n",
        "ax1.set_xlabel('x - (pixel value)')\n",
        "ax1.set_yticks(tf.range(0, 1.5, 0.5))\n",
        "ax1.set_xticks(tf.range(0, 1.5, 0.5))\n",
        "ax1.annotate('Baseline', xy=(0.0, 0.0), xytext=(0.0, 0.2),\n",
        "             arrowprops=dict(facecolor='black', shrink=0.1))\n",
        "ax1.annotate('Input', xy=(1.0, 0.0), xytext=(0.95, 0.2),\n",
        "             arrowprops=dict(facecolor='black', shrink=0.1))\n",
        "plt.show();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LswhGFd0xZBr"
      },
      "source": [
        "- **左**: ピクセル `x` のモデルの勾配は 0.0 から 0.8 の間で正ですが、0.8 から 1.0 の間で 0.0 になります。ピクセル `x{/ code2} は、モデルを真のクラスの予測確率 80% に向かって押す上で明らかに大きな影響を与えています。<em>ピクセル <code>x` の重要度が小さいか不連続であることは理にかなっていますか？\n",
        "\n",
        "- **右**: IG の背後にある直観は、ピクセル `x` のローカル勾配を累積し、モデルの全体的な出力クラス確率にどれだけ追加または減算するかを表すスコアとしてその重要性を属性付けすることです。IG は 3 つの部分に分けて計算することができます。\n",
        "\n",
        "    1. 0（基準点または開始点）と 1（入力ピクセルの値）の間の特徴空間の直線に沿って小さなステップを補間する\n",
        "    2. 各ステップで、各ステップに関するモデルの予測間の勾配を計算する\n",
        "    3. これらのローカル勾配を累積（累積平均）することで、基準と入力間の積分を近似する\n",
        "\n",
        "この直感を強化するために、以下の例の「消防艇」画像に IG を適用して、上記の 3 つの部分を説明します。 "
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "28MU35BCLM-s"
      },
      "source": [
        "### 基準の確立"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MIPG5yYfkydQ"
      },
      "source": [
        "基準は、特徴量重要度を計算する際の開始点として使用される入力画像です。直感的に、基準の説明的な役割を、入力画像に存在する場合の「消防艇」に対する各ピクセルの影響と比較する「消防艇」予測に対する各ピクセルの不在の影響を表していると考えることができます。結果として、基準の選択は、ピクセル特徴量重要度を解釈し、視覚化する上での中心的役割を果たします。基準の選択に関するその他の説明については、このチュートリアルの最後にある「次のステップ」に記載されたリソースをご覧ください。ここでは、ピクセル値がすべてゼロである黒い画像を使用します。\n",
        "\n",
        "ほかには、すべて白の画像、もしくはランダム画像といった選択肢を試すことができます。これは、`tf.random.uniform(shape=(224,224,3), minval=0.0, maxval=1.0)` で作成することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "wxvpwGkj4G4J"
      },
      "outputs": [],
      "source": [
        "baseline = tf.zeros(shape=(224,224,3))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vXRYwBWQS19B"
      },
      "outputs": [],
      "source": [
        "plt.imshow(baseline)\n",
        "plt.title(\"Baseline\")\n",
        "plt.axis('off')\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xphryu2mGAk8"
      },
      "source": [
        "### 式からコードへのアンパック"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QstMR0IcbfFA"
      },
      "source": [
        "次は、統合勾配の式です。\n",
        "\n",
        "$IntegratedGradients_{i}(x) ::= (x_{i} - x'*{i})\\times\\int*{\\alpha=0}^1\\frac{\\partial F(x'+\\alpha \\times (x - x'))}{\\partial x_i}{d\\alpha}$\n",
        "\n",
        "次のような意味があります。\n",
        "\n",
        "$_{i}$ = 特徴慮<br> $x$ = 入力<br> $x'$ = 基準<br> $\\alpha$ = 特徴を摂動させる補間定数"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_7w8SD5YMqvi"
      },
      "source": [
        "実践的には、定積分を計算するのは、必ずしも数値的に可能とは限らず、計算コストがかかる可能性があるため、次の数値近似を計算します。\n",
        "\n",
        "$IntegratedGrads^{approx}*{i}(x)::=(x*{i}-x'*{i})\\times\\sum*{k=1}^{m}\\frac{\\partial F(x' + \\frac{k}{m}\\times(x - x'))}{\\partial x_{i}} \\times \\frac{1}{m}$\n",
        "\n",
        "次のような意味があります。\n",
        "\n",
        "$*{i}$ = 特徴量（個別のピクセル）<br> $x$ = 入力（画像テンソル）<br> $x'$ = 基準（画像テンソル）<br> $k$ = スケーリングされた特徴摂動定数<br> $m$ = 積分のリーマン和近似のステップ数<br> $(x*{i}-x'_{i})$ = 基準との差を表す項。これは、統合勾配をスケーリングし、元の画像に関してそれらを維持するために必要です。基準画像から入力までのパスはピクセル空間内にあります。IG を使用して直線に積分しているため（線形変換）、これは、十分なステップ数のある $\\alpha$ に関し、補完された画像関数の微分の積分項におおよそ等しくなります。積分は、各ピクセルの勾配にパスに沿ったピクセルの変化を掛けたものを合計します。$x = (x0 + a(x1-x0))$ に置き換えて、この積分を 1 つの画像から別の画像への均一なステップとして実装する方が簡単であるため、変数の変更によって、$dx = (x1-x0)da$ となります。$(x1-x0)$ 項は定数であり、積分から因数分解されます。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5aPG68RssS2h"
      },
      "source": [
        "### 画像の補完"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pPrldEYsIR4M"
      },
      "source": [
        "$IntegratedGrads^{approx}*{i}(x)::=(x*{i}-x'*{i})\\times\\sum*{k=1}^{m}\\frac{\\partial F(\\overbrace{x' + \\frac{k}{m}\\times(x - x')}^\\text{interpolate m images at k intervals})}{\\partial x_{i}} \\times \\frac{1}{m}$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "y4r-ZrIIsdbI"
      },
      "source": [
        "まず、基準と元の画像の間に[線形補間](https://en.wikipedia.org/wiki/Linear_interpolation)を生成します。補間画像は、元の式の $\\alpha$ で表現される、基準と入力の間にある特徴空間の小さなステップとして考えることができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "I42mBKXyjcIc"
      },
      "outputs": [],
      "source": [
        "m_steps=50\n",
        "alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1) # Generate m_steps intervals for integral_approximation() below."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "7SWLSFOHsbgh"
      },
      "outputs": [],
      "source": [
        "def interpolate_images(baseline,\n",
        "                       image,\n",
        "                       alphas):\n",
        "  alphas_x = alphas[:, tf.newaxis, tf.newaxis, tf.newaxis]\n",
        "  baseline_x = tf.expand_dims(baseline, axis=0)\n",
        "  input_x = tf.expand_dims(image, axis=0)\n",
        "  delta = input_x - baseline_x\n",
        "  images = baseline_x +  alphas_x * delta\n",
        "  return images"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "s4zFzbUBj684"
      },
      "source": [
        "上記の関数を使用して、黒い基準画像と例の「消防艇」画像の間に、アルファ間隔で、線形パスに沿った補間画像を生成してみましょう。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NgVx8swDQtTl"
      },
      "outputs": [],
      "source": [
        "interpolated_images = interpolate_images(\n",
        "    baseline=baseline,\n",
        "    image=img_name_tensors['Fireboat'],\n",
        "    alphas=alphas)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QABFsuCvkO1h"
      },
      "source": [
        "補完画像を視覚化してみましょう。注意: $\\alpha$ 定数は、各補間画像の強度が一貫して増加しているとも考えられます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9tmBGdnHAupk"
      },
      "outputs": [],
      "source": [
        "fig = plt.figure(figsize=(20, 20))\n",
        "\n",
        "i = 0\n",
        "for alpha, image in zip(alphas[0::10], interpolated_images[0::10]):\n",
        "  i += 1\n",
        "  plt.subplot(1, len(alphas[0::10]), i)\n",
        "  plt.title(f'alpha: {alpha:.1f}')\n",
        "  plt.imshow(image)\n",
        "  plt.axis('off')\n",
        "\n",
        "plt.tight_layout();"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "h7T0f1cqsaxA"
      },
      "source": [
        "### 勾配の計算"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tps0eWc0REqL"
      },
      "source": [
        "特徴量の変化とモデルの予測の変化の関係を測定するために、勾配をどのように計算するか、見てみましょう。画像の場合、勾配は、どのピクセルがモデルが予測したクラスの確率に最も大きな効果があるかを示します。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ouuVIsdfgukW"
      },
      "source": [
        "$IntegratedGrads^{approx}*{i}(x)::=(x*{i}-x'*{i})\\times\\sum*{k=1}^{m}\\frac{\\overbrace{\\partial F(\\text{interpolated images})}^\\text{compute gradients}}{\\partial x_{i}} \\times \\frac{1}{m}$\n",
        "\n",
        "次のような意味があります。<br> $F()$ = モデルの予測関数<br> $\\frac{\\partial{F}}{\\partial{x_i}}$ = 各特徴量 $x_i$ に関するモデル F の予測関数の勾配（偏微分 $\\partial$ のベクトル）"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hY_Ok3CoJW1W"
      },
      "source": [
        "TensorFlow では、[`tf.GradientTape`](https://www.tensorflow.org/api_docs/python/tf/GradientTape) を使用して、簡単に勾配を計算できます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JW1O9qEsxZOP"
      },
      "outputs": [],
      "source": [
        "def compute_gradients(images, target_class_idx):\n",
        "  with tf.GradientTape() as tape:\n",
        "    tape.watch(images)\n",
        "    logits = model(images)\n",
        "    probs = tf.nn.softmax(logits, axis=-1)[:, target_class_idx]\n",
        "  return tape.gradient(probs, images)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9BfRuzx4-c87"
      },
      "source": [
        "正しい出力に関し、補間パスに沿った各画像の勾配を計算してみましょう。モデルが、各クラスの予測確率に変換するロジットを含む `(1, 1001)` 形状の `Tensor` を返すことを思い出してください。正しい ImageNet ターゲットクラスインデックスを画像の ` compute_gradients` 関数に渡す必要があります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kHIR58rNJ3q_"
      },
      "outputs": [],
      "source": [
        "path_gradients = compute_gradients(\n",
        "    images=interpolated_images,\n",
        "    target_class_idx=tf.constant(555))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JJodSbDUQ3T_"
      },
      "source": [
        "`(n_interpolated_images, img_height, img_width, RGB)` 出力形状に注意してください。これは、補間パスに沿った画像の各ピクセルの勾配です。これらの勾配を、特徴空間内の小さなステップに対する、モデルの予測における変化を測定するものとして考えることができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "v2rpO2JTbQId"
      },
      "outputs": [],
      "source": [
        "print(path_gradients.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "zmaPpr5bUtbr"
      },
      "source": [
        "**勾配の飽和を視覚化する**\n",
        "\n",
        "上記で計算した勾配は、モデルの「消防艇」の予測確率への*ローカル*変化を説明したものであり、*飽和*する可能性があることを思い出してください。\n",
        "\n",
        "これらの概念は、次の 2 つの図に示すように、上記で計算した勾配を使用して視覚化されます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "mCH8sAf3TTJ2"
      },
      "outputs": [],
      "source": [
        "pred = model(interpolated_images)\n",
        "pred_proba = tf.nn.softmax(pred, axis=-1)[:, 555]\n",
        "\n",
        "plt.figure(figsize=(10, 4))\n",
        "ax1 = plt.subplot(1, 2, 1)\n",
        "ax1.plot(alphas, pred_proba)\n",
        "ax1.set_title('Target class predicted probability over alpha')\n",
        "ax1.set_ylabel('model p(target class)')\n",
        "ax1.set_xlabel('alpha')\n",
        "ax1.set_ylim([0, 1])\n",
        "\n",
        "ax2 = plt.subplot(1, 2, 2)\n",
        "# Average across interpolation steps\n",
        "average_grads = tf.reduce_mean(path_gradients, axis=[1, 2, 3])\n",
        "# Normalize gradients to 0 to 1 scale. E.g. (x - min(x))/(max(x)-min(x))\n",
        "average_grads_norm = (average_grads-tf.math.reduce_min(average_grads))/(tf.math.reduce_max(average_grads)-tf.reduce_min(average_grads))\n",
        "ax2.plot(alphas, average_grads_norm)\n",
        "ax2.set_title('Average pixel gradients (normalized) over alpha')\n",
        "ax2.set_ylabel('Average pixel gradients')\n",
        "ax2.set_xlabel('alpha')\n",
        "ax2.set_ylim([0, 1]);"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-ntMpA87jNN6"
      },
      "source": [
        "- **左**: この図は、モデルの「消防艇」クラスに対する信頼度がアルファでどのように変化するのかを示します。勾配または線の傾きが、最後の「消防艇」予測確率の約 40% に一定する前に、0.6 と 1.0 の間で大きく平たん化または飽和していることに注目してください。\n",
        "\n",
        "- **右**: 右の図は、アルファの平均勾配の大きさをより直接的に示しています。値が 0 に急接近し、わずかながら 0 を下回っている様子に注目してください。実際、これらのピクセル勾配をゼロにすることで、モデルがピクセル（放水器）を学習したとしてユーザーが直感的に考える前に、モデルはアルファの低い値での勾配から一番「学習」して正しい予測を立てますが、アルファ値が元の入力画像に近づくにつれ、非常に不確実で、偽の船橋または放水のピクセルに焦点を当て続けます。\n",
        "\n",
        "これらの重要な放水器のピクセルが「消防艇」予測に重要なものとして反映されるようにするには、これらの勾配を累積して、各ピクセルが「消防艇」の予測確率にどれほどの影響を与えるのかを正確に概算する方法について、さらにお読みください。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LQdACCM6sJdW"
      },
      "source": [
        "### 勾配の累積（積分近似）"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QHopk9evmO5P"
      },
      "source": [
        "IG の積分の数値近似を計算するにはさまざまな方法があり、関数の精度と収束のトレードオフもそれぞれに異なります。メソッドの一般的なクラスは、[リーマン和](https://en.wikipedia.org/wiki/Riemann_sum)と呼ばれます。ここでは、台形近似の法則を使用します（このチュートリアルの最後に、さまざまな近似方法を確認できるように、コードを追加しています）。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "GshPZQgROs80"
      },
      "source": [
        "$IntegratedGrads^{approx}*{i}(x)::=(x*{i}-x'*{i})\\times \\overbrace{\\sum*{k=1}^{m}}^\\text{Sum m local gradients} \\text{gradients(interpolated images)} \\times \\overbrace{\\frac{1}{m}}^\\text{Divide by m steps}$\n",
        "\n",
        "この式から、`m` 勾配で加算して、`m` ステップで除算していることがわかります。この 2 つの演算を 3 番目の<em data-md-type=\"emphasis\">入力画像間の `m` 個の補間予測のロカール勾配の平均</em>として実装することができます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "1cMVl-Grx3lp"
      },
      "outputs": [],
      "source": [
        "def integral_approximation(gradients):\n",
        "  # riemann_trapezoidal\n",
        "  grads = (gradients[:-1] + gradients[1:]) / tf.constant(2.0)\n",
        "  integrated_gradients = tf.math.reduce_mean(grads, axis=0)\n",
        "  return integrated_gradients"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QVQAHunkW79t"
      },
      "source": [
        "`integral_approximation` 関数は、基準と元の画像間の補間画像に関するターゲットクラスの確率の勾配を取ります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "JeF01fydNq0I"
      },
      "outputs": [],
      "source": [
        "ig = integral_approximation(\n",
        "    gradients=path_gradients)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "7XVItLpurOAM"
      },
      "source": [
        "`m` 個の補間画像の勾配を平均化すると、元の「ジャイアントパンダ」画像と同じ形状の補間勾配テンソルが返されることを確認できます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "z1bP6l3ahfyn"
      },
      "outputs": [],
      "source": [
        "print(ig.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "C1ODXUevyGxL"
      },
      "source": [
        "### すべてを統合する"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "NcaTR-x8v1At"
      },
      "source": [
        "これまでの 3 つの部分を `IntegratedGradients` 関数に合わせて、[@tf.function](https://www.tensorflow.org/guide/function) デコレータを使用して、高性能のコーラブル TensorFlow グラフにコンパイルします。これは、以下のように、5 つの小さなステップとして実装されます。\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5YdWHscoovhk"
      },
      "source": [
        "$IntegratedGrads^{approx}*{i}(x)::=\\overbrace{(x*{i}-x'*{i})}^\\text{5.}\\times \\overbrace{\\sum*{k=1}^{m}}^\\text{4.} \\frac{\\partial \\overbrace{F(\\overbrace{x' + \\overbrace{\\frac{k}{m}}^\\text{1.}\\times(x - x'))}^\\text{2.}}^\\text{3.}}{\\partial x_{i}} \\times \\overbrace{\\frac{1}{m}}^\\text{4.}$"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "pjdfjp_3pHiY"
      },
      "source": [
        "1. アルファを生成します。$\\alpha$\n",
        "\n",
        "2. 補間画像を生成します。$(x' + \\frac{k}{m}\\times(x - x'))$\n",
        "\n",
        "3. 入力特徴に関してモデル $F$ 出力予測間の勾配を計算します。$\\frac{\\partial F(\\text{interpolated path inputs})}{\\partial x_{i}}$\n",
        "\n",
        "4. 積分近似の平均を計算します。$\\sum_{k=1}^m \\text{gradients} \\times \\frac{1}{m}$\n",
        "\n",
        "5. 元の画像に関する統合勾配をスケーリングします。$(x_{i}-x'_{i}) \\times \\text{integrated gradients}$。このステップが必要なのは、複数の補間画像で累積された属性値が同じユニットにあり、元の画像のピクセル重要度を厳密に表すようにするためです。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O_H3k9Eu7Rl5"
      },
      "outputs": [],
      "source": [
        "@tf.function\n",
        "def integrated_gradients(baseline,\n",
        "                         image,\n",
        "                         target_class_idx,\n",
        "                         m_steps=50,\n",
        "                         batch_size=32):\n",
        "  # 1. Generate alphas.\n",
        "  alphas = tf.linspace(start=0.0, stop=1.0, num=m_steps+1)\n",
        "\n",
        "  # Initialize TensorArray outside loop to collect gradients.    \n",
        "  gradient_batches = tf.TensorArray(tf.float32, size=m_steps+1)\n",
        "    \n",
        "  # Iterate alphas range and batch computation for speed, memory efficiency, and scaling to larger m_steps.\n",
        "  for alpha in tf.range(0, len(alphas), batch_size):\n",
        "    from_ = alpha\n",
        "    to = tf.minimum(from_ + batch_size, len(alphas))\n",
        "    alpha_batch = alphas[from_:to]\n",
        "\n",
        "    # 2. Generate interpolated inputs between baseline and input.\n",
        "    interpolated_path_input_batch = interpolate_images(baseline=baseline,\n",
        "                                                       image=image,\n",
        "                                                       alphas=alpha_batch)\n",
        "\n",
        "    # 3. Compute gradients between model outputs and interpolated inputs.\n",
        "    gradient_batch = compute_gradients(images=interpolated_path_input_batch,\n",
        "                                       target_class_idx=target_class_idx)\n",
        "    \n",
        "    # Write batch indices and gradients to extend TensorArray.\n",
        "    gradient_batches = gradient_batches.scatter(tf.range(from_, to), gradient_batch)    \n",
        "  \n",
        "  # Stack path gradients together row-wise into single tensor.\n",
        "  total_gradients = gradient_batches.stack()\n",
        "\n",
        "  # 4. Integral approximation through averaging gradients.\n",
        "  avg_gradients = integral_approximation(gradients=total_gradients)\n",
        "\n",
        "  # 5. Scale integrated gradients with respect to input.\n",
        "  integrated_gradients = (image - baseline) * avg_gradients\n",
        "\n",
        "  return integrated_gradients"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "8G0ELl_wRrd0"
      },
      "outputs": [],
      "source": [
        "ig_attributions = integrated_gradients(baseline=baseline,\n",
        "                                       image=img_name_tensors['Fireboat'],\n",
        "                                       target_class_idx=555,\n",
        "                                       m_steps=240)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "P-LSHD2sajFf"
      },
      "source": [
        "もう一度、IG 特徴量属性の形状が入力「消防艇」画像と同じであることを確認できます。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "beoEunC5aZOa"
      },
      "outputs": [],
      "source": [
        "print(ig_attributions.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fLaB21txJhMq"
      },
      "source": [
        "論文では、サンプルに応じて、20 から 300 個のステップ数が推奨されています（積分を正確に近似するには、実際には、1,000 個以上となることがあります）。適切なステップ数を確認するためのその他のコードは、このチュートリアルの最後にある「次のステップ」のリソースをご覧ください。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "o55W6NYXGSZ8"
      },
      "source": [
        "### 属性の視覚化"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XSQ6Y-DZvrQu"
      },
      "source": [
        "属性を視覚化し、元の画像に重ねる準備が整いました。以下のコードでは、属性マスクを作成するために、カラーチャンネルの統合勾配の絶対値を加算しています。この描画方法では、モデルの予測に対する相対的な影響がキャプチャされます。 "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4QN2cEA_WFym"
      },
      "outputs": [],
      "source": [
        "def plot_img_attributions(baseline,\n",
        "                          image,\n",
        "                          target_class_idx,\n",
        "                          m_steps=tf.constant(50),\n",
        "                          cmap=None,\n",
        "                          overlay_alpha=0.4):\n",
        "\n",
        "  attributions = integrated_gradients(baseline=baseline,\n",
        "                                      image=image,\n",
        "                                      target_class_idx=target_class_idx,\n",
        "                                      m_steps=m_steps)\n",
        "\n",
        "  # Sum of the attributions across color channels for visualization.\n",
        "  # The attribution mask shape is a grayscale image with height and width\n",
        "  # equal to the original image.\n",
        "  attribution_mask = tf.reduce_sum(tf.math.abs(attributions), axis=-1)\n",
        "\n",
        "  fig, axs = plt.subplots(nrows=2, ncols=2, squeeze=False, figsize=(8, 8))\n",
        "\n",
        "  axs[0, 0].set_title('Baseline image')\n",
        "  axs[0, 0].imshow(baseline)\n",
        "  axs[0, 0].axis('off')\n",
        "\n",
        "  axs[0, 1].set_title('Original image')\n",
        "  axs[0, 1].imshow(image)\n",
        "  axs[0, 1].axis('off')\n",
        "\n",
        "  axs[1, 0].set_title('Attribution mask')\n",
        "  axs[1, 0].imshow(attribution_mask, cmap=cmap)\n",
        "  axs[1, 0].axis('off')\n",
        "\n",
        "  axs[1, 1].set_title('Overlay')\n",
        "  axs[1, 1].imshow(attribution_mask, cmap=cmap)\n",
        "  axs[1, 1].imshow(image, alpha=overlay_alpha)\n",
        "  axs[1, 1].axis('off')\n",
        "\n",
        "  plt.tight_layout()\n",
        "  return fig"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "n73VxzbxeMvD"
      },
      "source": [
        "「消防艇」画像の属性を見ると、モデルは放水器と放水を正しい予測に貢献するものとして識別しているのがわかります。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vxCQFx96iDVs"
      },
      "outputs": [],
      "source": [
        "_ = plot_img_attributions(image=img_name_tensors['Fireboat'],\n",
        "                          baseline=baseline,\n",
        "                          target_class_idx=555,\n",
        "                          m_steps=2400,\n",
        "                          cmap=plt.cm.inferno,\n",
        "                          overlay_alpha=0.4)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lo4SncDZfTw0"
      },
      "source": [
        "「ジャイアントパンダ」の画像では、属性はテクスチャ、鼻、およびパンダの顔の毛がハイライトされています。"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TcpGLJWuHnYl"
      },
      "outputs": [],
      "source": [
        "_ = plot_img_attributions(image=img_name_tensors['Giant Panda'],\n",
        "                          baseline=baseline,\n",
        "                          target_class_idx=389,\n",
        "                          m_steps=1100,\n",
        "                          cmap=plt.cm.viridis,\n",
        "                          overlay_alpha=0.5)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "H3etJZHuI6hX"
      },
      "source": [
        "## 使用と制限"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xEX4Jh2uLvxA"
      },
      "source": [
        "ユースケース\n",
        "\n",
        "- モデルを展開する前に統合勾配のようなテクニックを使用すると、それがなぜ、どのように機能するのかに対する直感を得る上で役立ちます。このテクニックでハイライトされる特徴量は、直感と一致しているでしょうか。していない場合は、モデルやデータセットのバグか、過適合の可能性があります。\n",
        "\n",
        "制限事項\n",
        "\n",
        "- 統合勾配では、個々のサンプルの特徴量重要度を得られますが、全データセットの全体的な特徴量重要度を知ることができません。\n",
        "\n",
        "- 統合勾配では、個々の特徴量重要度を得られますが、特徴量の相互作用や組み合わせを説明しません。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Ejc2Ho_8i162"
      },
      "source": [
        "## 次のステップ"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "G8Ra5ijj7pEc"
      },
      "source": [
        "このチュートリアルでは、統合勾配の基本的な実装を説明しました。次のステップとして、このノートブックを参考にしながら、異なるモデルと画像を使ってこのテクニックを試してみることができます。\n",
        "\n",
        "興味のある方には、[こちら](https://github.com/GoogleCloudPlatform/training-data-analyst/tree/master/blogs/integrated_gradients)にさらに詳しいチュートリアル（異なる基準を使ったコード、積分近似の計算、十分なステップ数の決定が含まれます）が用意されています。 <br>.\n",
        "\n",
        "理解を深めるには、論文「[Axiomatic Attribution for Deep Networks](https://arxiv.org/abs/1703.01365)」と、TensorFlow の前のバージョンでの実装が含まれた [Github リポジトリ](https://github.com/ankurtaly/Integrated-Gradients)をご覧ください。また、[distill.pub](https://distill.pub/2020/attribution-baselines/) では、特徴量属性、および異なる基準の影響を調べることができます。\n",
        "\n",
        "実稼働の機械学習ワークフローに IG を統合して特徴量重大度、モデルの誤差分析、およびデータの歪み監視を行うことをお考えですか？IG 属性をサポートする Google Cloud の[説明可能な AI](https://cloud.google.com/explainable-ai)製品をご覧ください。Google AI PAIR リサーチグループでは、IG 特徴量属性の視覚化を含む、モデルのデバッグに使用できる [What-if ツール](https://pair-code.github.io/what-if-tool/index.html#about)もオープンソースとして提供しています。"
      ]
    }
  ],
  "metadata": {
    "accelerator": "GPU",
    "colab": {
      "collapsed_sections": [],
      "name": "integrated_gradients.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
